package trip

import (
	"context"
	rentalpb "coolcar/proto/rental_out/v1"
	"fmt"
	"time"

	"go.mongodb.org/mongo-driver/bson"

	"go.mongodb.org/mongo-driver/bson/primitive"
	"go.mongodb.org/mongo-driver/mongo"
)

type Mongo struct {
	col      *mongo.Collection
	newObjID func() primitive.ObjectID
	nowFunc  func() time.Time
}

func NewMongo(db *mongo.Database) *Mongo {
	return &Mongo{
		col:      db.Collection("trip"),
		newObjID: primitive.NewObjectID,
		nowFunc:  time.Now,
	}
}

type TripRecord struct {
	ID        primitive.ObjectID `bson:"_id"`
	UpdatedAt int64              `bson:"updatedat"`
	Trip      *rentalpb.Trip     `bson:"trip"`
}

func (m *Mongo) CreateTrip(c context.Context, trip *rentalpb.Trip) (*TripRecord, error) {
	r := TripRecord{
		Trip: trip,
	}
	r.ID = m.newObjID()
	r.UpdatedAt = m.nowFunc().UnixNano()

	_, err := m.col.InsertOne(c, r)
	if err != nil {
		return nil, err
	}

	return &r, nil
}

func (m *Mongo) GetTrip(c context.Context, id string, accountID string) (*TripRecord, error) {
	objID, err := primitive.ObjectIDFromHex(id)
	if err != nil {
		return nil, fmt.Errorf("invalid id: %v \n", err)
	}

	res := m.col.FindOne(c, bson.M{
		"_id":            objID,
		"trip.accountid": accountID,
	})

	if err := res.Err(); err != nil {
		return nil, err
	}

	var t TripRecord
	err = res.Decode(&t)
	if err != nil {
		return nil, fmt.Errorf("can not decode: %v \n", err)
	}

	return &t, err
}

func (m *Mongo) GetTrips(c context.Context, accountID string, status rentalpb.TripStatus) ([]*TripRecord, error) {
	filter := bson.M{
		"trip.accountid": accountID,
	}

	if status != rentalpb.TripStatus_TS_NOT_SPECIFIED {
		filter["trip.status"] = status
	}

	cur, err := m.col.Find(c, filter)
	if err != nil {
		return nil, err
	}

	var trips []*TripRecord
	for cur.Next(c) {
		var trip TripRecord
		err := cur.Decode(&trip)
		if err != nil {
			return nil, err
		}
		trips = append(trips, &trip)
	}

	return trips, nil
}

func (m *Mongo) UpdateTrip(c context.Context, tripID string, accountID string, updateAt int64, trip *rentalpb.Trip) error {
	// 使用乐观锁更新行程
	objID, _ := primitive.ObjectIDFromHex(tripID)
	res, err := m.col.UpdateOne(c, bson.M{
		"_id":            objID,
		"trip.accountid": accountID,
		"updatedat":      updateAt,
	}, bson.M{
		"$set": bson.M{
			"trip":      trip,
			"updatedat": time.Now().Unix(),
		},
	})
	if err != nil {
		return err
	}
	if res.MatchedCount == 0 {
		return mongo.ErrNoDocuments
	}
	return err
}
